欢迎来到 Werkzeug 教程,我们将会实现一个类似 TinyURL 的网站来储存 URLS。我们将会使用的库有模板引擎 Jinja 2,数据层支持 redis ,当然还有 WSGI 协议层 Werkzeug。
你可以使用 pip 来安装依赖库:
pip install Jinja2 redis同时确定你的本地开启一个 redis 服务,如果你是OS X系统,你可以使用 brew 来安装 redis:
brew install redis如果你是用 Ubuntu 或 Debian, 你可以使用 apt-get:
sudo apt-get install redisRedis 专为 UNIX 系统开发,并没有考虑为 Windows 设计。但对于开发来说,非官方的版本已经足够了,你可以从 github 得到它。
简短介绍¶在这个教程中,我们将一起用 Werkzeug 创建一个短网址服务。请注意,Werkzeug 并不是一个框架,它是一个 WSGI 工具集的库,你可以通过它来创建你自己的框架或 Web 应用。Werkzeug 是非常灵活的,这篇教程用到的一些方法只是 Werkzeug 的一部分。
在数据层,为了保持简单,我们使用 redis 来代替关系型数据库,而且 redis 也擅长来做这些。
最终的结果将会看起来像这样:
Step 0: WSGI 基础介绍¶Werkzeug 是一个 WSGI 工具包。WSGI 是一个 Web 应用和服务器通信的协议,Web 应用可以通过 WSGI 一起工作。
一个基本的 “Hello World” WSGI 应用看起来是这样的:
def application(environ, start_response):start_response('200 OK', [('Content-Type', 'text/plain')])return ['Hello World!']用过 WSGI 应用可以和环境通信,他有一个可调用的 start_response 。环境包含了所有进来的信息。 start_response 用来表明已经收到一个响应。通过 Werkzeug 你可以不必直接处理请求或者响应这些底层的东西,它已经为你封装好了这些。
请求数据需要环境对象,Werkzeug 允许你以一个轻松的方式访问数据。响应对象是一个 WSGI应用,提供了更好的方法来创建响应。
下面教你怎么用响应对象来写一个应用:
from werkzeug.wrappers import Response def application(environ, start_response):response = Response('Hello World!', mimetype='text/plain')return response(environ, start_response)这里有一个在 URL 中查询字符串的扩展版本(重点是 URL 中的 name 将会替代World):
from werkzeug.wrappers import Request, Responsedef applicatio n(environ, start_response):request = Request(environ)text = 'Hello %s!' % request.args.get('name', 'World')response = Response(text, mimetype='text/plain')return response(environ, start_response)到此为止,你已经足够了解 WSGI 了。
Step 1: 创建目录¶在开始之前,首先为应用创建一个目录:
/shortly/static/templates这个简洁的目录不是一个python包,他用来存放我们的项目文件。我们的入口模块将会放在 /shortly目录的根目录下。 /static 目录用来放置CSS、Javascript等静态文件,用户可以通过HTTP协议直接访问。 /templates 目录用来存放 Jinja2 模板文件,接下来你为项目创建的模板文件将要放到这个文件夹内。
Step 2: 基本结构¶现在我们正式开始为我们的项目创建模块。在 shortly 目录创建 shortly.py 文件。首先来导入一些东西。为了防止混淆,我把所有的入口放在这,即使他们不会立即使用:
import osimport redisimport urlparsefrom werkzeug.wrappers import Request, Responsefrom werkzeug.routing import Map, Rulefrom werkzeug.exceptions import HTTPException, NotFoundfrom werkzeug.wsgi import SharedDataMiddlewarefrom werkzeug.utils import redirectfrom jinja2 import Environment, FileSystemLoader接下来我们来为我们的应用创建基本的结构,并通过一个函数来创建应用实例,通过 WSGI中间件输出 static 目录的文件:
class Shortly(object):def __init__(self, config):self.redis = redis.Redis(config['redis_host'], config['redis_port'])def dispatch_request(self, request):return Response('Hello World!')def wsgi_app(self, environ, start_response):request = Request(environ)response = self.dispatch_request(request)return response(environ, start_response)def __call__(self, environ, start_response):return self. wsgi_app(environ, start_response)def create_app(redis_host='localhost', redis_port=6379, with_static=True):app = Shortly({'redis_host':redis_host,'redis_port':redis_port})if with_static:app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {'/static': os.path.join(os.path.dirname(__file__), 'static')})return app最后我们添加一部分代码来开启一个本地服务器,自动加载代码并开启调试器:
if __name__ == '__main__':from werkzeug.serving import run_simpleapp = create_app()run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)思路很简单,我们的 Shortly 是一个实际的 WSGI 应用。 __call__ 方法直接调用 wsgi_app 。这样做我们可以装饰 wsgi_app 调用中间件,就像我们在 create_app函数中做的一样。 wsgi_app 实际上创建了一个 Request 对象,之后通过dispatch_request 调用 Request 对象然后给 WSGI 应用返回一个 Response对象。正如你看到的:无论是创建 Shortly 类,还是还是创建 Werkzeug Request 对象来执行 WSGI 接口。最终结果只是从 dispatch_request 方法返回另一个 WSGI 应用。
create_app 可以被用于创建一个新的应用实例。他不仅可以通过参数配置应用,还可以选择性的添加中间件来输出静态文件。通过这种方法我们甚至可以不配置服务器就能访问静态文件,这对开发是很有帮助的。
插曲: 运行应用程序¶现在你应该可以通过 python 执行这个文件了,看看你本机的服务:
$ python shortly.py * Running on http://127.0.0.1:5000/ * Restarting with reloader: stat() polling它告诉你自动加载已经开启,他会通过各种各样的技术来判断硬盘上的文件是否改变来自动重启。
在浏览器输入这个URL,你将会看到 “Hello World!”。
Step 3: 环境¶现在我们已经有了一个应用的基本类,可以通过构造函数来实现一些功能。通过构造函数我们可以渲染模板、连接redis。现在让我们扩展这个类:
def __init__(self, config):self.redis = redis.Redis(config['redis_host'], config['redis_port'])template_path = os.path.join(os.path.dirname(__file__), 'templates')self.jinja_env = Environment(loader=FileSystemLoader(template_path), autoescape=True)def render_template(self, template_name, **context):t = self.jinja_env.get_template(template_name)return Response(t.render(context), mimetype='text/html')Step 4: 路由¶下一步是路由。我们可以通过路由来匹配和解析URL。Werkzeug 提供了一个灵活的集成路由。你需要创建一个 Map 实例并添加一系列 Rule对象。每个 rule 将会匹配 URL 并添加一个 “endpoint”。endpoint 通常是一个用于标记URL 的字符串。此外我们还可以使用它来翻转 URL,但这不是这篇教程我们要做的。
把下列代码放入构造函数:
self.url_map = Map([Rule('/', endpoint='new_url'),Rule('/', endpoint='follow_short_link'),Rule('/+', endpoint='short_link_details')])现在我们创造了一个包含三个 URL 规则的字典。第一个规则, / 是根 URL 空间,我们可以调用一个逻辑函数来创建一个新 URL;第二个规则,根据规则指向一个目标URL;最后一个规则,和第二个有相同的规则,但是它在最后添加一个(+)来显示